﻿/*** FFIExisting Class - FlashFurc Core
 * ---
 * Author: IceDragon <icedragon@quickfox.org>
 * Web:    www.icerealm.org
 * ---
 * 
 * Copyright (c) 2010, IceRealm.org
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *   # Redistributions of source code must retain the above copyright notice,
 *     this list of conditions and the following disclaimer.
 *   # Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *   # Neither the name of the IceRealm.org nor the names of its contributors
 *     may be used to endorse or promote products derived from this software
 *     without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

using System.Collections.Generic;
using System.IO;

using Furcadia;

namespace FlashFurc
{
    /// <summary>
    /// (FlashFurc Installer)
    /// This class handles Furcadia installation from an existing local install
    /// to the target medium.
    /// </summary>
    public class FFIExisting : FlashFurcInstaller
    {
        /*** Constructors ****************************************************/
        public FFIExisting() : base()
        {
            CheckAvailability();
        }

        /*** Methods *********************************************************/
        public override void Run()
        {
            // If this procedure isn't available, we can't start.
            if (!Available && !CheckAvailability())
                throw new NotAvailableException();

            // Nor can we start if the target was unspecified.
            if (Settings.InstallTarget == null)
                throw new NotAvailableException("Install target not specified!");

            busy = true;

            // Check the files and prepare to copy data.
            Status = "Collecting source files information...";
            CheckFiles();

            // Create the necessary folders on the target medium.
            Status = "Creating main directory...";
            DirectoryInfo diRoot = new DirectoryInfo(Settings.InstallTarget);
            if (!diRoot.Exists)
                diRoot.Create();

            DirectoryInfo diData = diRoot.CreateSubdirectory(DATA_FOLDER);

            // Transfer the files from the install path to the target medium.
            Status = "Preparing to copy files...";
            DirectoryInfo diInstallPath = new DirectoryInfo(Paths.GetFurcadiaInstallPath());
            _CopyPath(diInstallPath, diRoot, true);

            // Transfer the settings if necessary.
            if (Settings.CopySettings)
            {
                _CopyPath(
                    new DirectoryInfo(Paths.SettingsPath),
                    diData.CreateSubdirectory("settings")
                );
            }

            // Transfer the logs if necessary.
            if (Settings.CopyLogs)
            {
                _CopyPath(
                    new DirectoryInfo(Paths.LogsPath),
                    diData.CreateSubdirectory("logs")
                );
            }

            // Transfer the cache if necessary.
            if (Settings.CopyCacheData)
            {
                _CopyPath(
                    new DirectoryInfo(Paths.CachePath + Path.DirectorySeparatorChar + "Temporary Dreams"),
                    diData.CreateSubdirectory("tmp")
                );

                _CopyPath(
                    new DirectoryInfo(Paths.CachePath + Path.DirectorySeparatorChar + "Permanent Maps"),
                    diData.CreateSubdirectory("maps")
                );

                _CopyPath(
                    new DirectoryInfo(Paths.CachePath + Path.DirectorySeparatorChar + "Dynamic Avatars"),
                    diData.CreateSubdirectory("skins")
                );

                _CopyPath(
                    new DirectoryInfo(Paths.CachePath + Path.DirectorySeparatorChar + "Portrait Cache"),
                    diData.CreateSubdirectory("portraits")
                );

                _CopyPath(
                    new DirectoryInfo(Paths.CachePath + Path.DirectorySeparatorChar + "Temporary Files"),
                    diData.CreateSubdirectory("tmp")
                );
            }

            // Copy over the character files.
            string data_path = diData.FullName;
            foreach (FileInfo fi in CharacterFiles)
            {
                Status = "Copying character file " + fi.Name;

                // If we end up having duplicate files, make sure both end up
                // inside.
                string filename;
                int i = 1;
                do
                {
                    string name = fi.Name.Split('.')[0] +
                        ((i > 1) ? i.ToString() : "") +
                        fi.Extension;

                    filename = data_path + Path.DirectorySeparatorChar + name;
                } while (File.Exists(filename));

                fi.CopyTo(filename);
            }

            // Create the localdir.ini and fill it with a relative path.
            CreateLocaldir(diRoot);

            // All done! (hopefully)
            busy = false;
        }


        #region Private Stuff
        /*** Methods *********************************************************/
        private bool CheckAvailability()
        {
            // Can we obtain the Furcadia install path? If not, it is likely
            // either not installed, or is installed in an unsupported way.
            string install_path = Paths.GetFurcadiaInstallPath();
            Available = install_path != null;
            if (Available)
            {
                // Check if the path exists and that at least the Furcadia.exe
                // is in place. For more than that, we'd have to trust that
                // the user didn't mess around with it.
                Available = Directory.Exists(install_path) &&
                    File.Exists(install_path + Path.DirectorySeparatorChar + "Furcadia.exe");
            }

            return Available;
        }
        private void CheckFiles()
        {
            TotalFiles = 0;
            TotalSize = 0;

            // Go through the main Furcadia folder first of all.
            string path = Paths.GetFurcadiaInstallPath();
            if (path == null)
                throw new NotAvailableException();
            _CheckPath(new DirectoryInfo(path), true);

            // Go through the settings if necessary.
            if (Settings.CopySettings)
                _CheckPath(new DirectoryInfo(Paths.SettingsPath));

            // Go through the cache if necessary.
            if (Settings.CopyCacheData)
                _CheckPath(new DirectoryInfo(Paths.CachePath));

            // Go through the logs if necessary.
            if (Settings.CopyLogs)
                _CheckPath(new DirectoryInfo(Paths.LogsPath));
        }

        private void _CheckPath(DirectoryInfo di)
        {
            _CheckPath(di, false);
        }
        private void _CheckPath(DirectoryInfo di, bool applyFilters)
        {
            if (!di.Exists)
                return;

            if (!applyFilters)
            {
                foreach (FileInfo fi in di.GetFiles())
                {
                    TotalFiles++;
                    TotalSize += (ulong)fi.Length;
                }
            }
            else
            {
                foreach (FileInfo fi in di.GetFiles())
                {
                    if (ShouldCopy(fi))
                    {
                        TotalFiles++;
                        TotalSize += (ulong)fi.Length;
                    }
                }
            }

            foreach (DirectoryInfo sdi in di.GetDirectories())
                _CheckPath(sdi, applyFilters);
        }

        private void _CopyPath(DirectoryInfo source, DirectoryInfo target)
        {
            _CopyPath(source, target, false);
        }
        private void _CopyPath(DirectoryInfo source, DirectoryInfo target, bool applyFilters)
        {
            if (!source.Exists)
                return;

            if (!target.Exists)
                target.Create();

            foreach (FileInfo fi in source.GetFiles())
            {
                if (applyFilters && ShouldCopy(fi))
                {
                    Status = "Copying " + fi.Name + "...";
                    fi.CopyTo(target.FullName + Path.DirectorySeparatorChar + fi.Name);
                    CurrentFile++;
                    CurrentSize += (ulong)fi.Length;
                }
            }
            foreach (DirectoryInfo di in source.GetDirectories())
                _CopyPath(di, target.CreateSubdirectory(di.Name), applyFilters);
        }

        /*** Class Functions *************************************************/
        /// <summary>
        /// Should the given file be copied over to the new medium?
        /// </summary>
        /// <param name="fi">File info to check</param>
        /// <returns>TRUE if the given file should be copied. FALSE otherwise.</returns>
        private static bool ShouldCopy(FileInfo fi)
        {
            return !(
                fi.Extension == ".lnk" ||
                (fi.Extension == ".ini" && !CharacterScanner.IsCharacterFile(fi)) ||
                IGNORED_FILES.Contains(fi.Name.ToLower())
            );
        }

        /*** Members *********************************************************/
        #endregion
    }
}
